﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Emulator.Base;
using Emulator.Base.CPU;
using Emulator.GameBoyConfig;
using Emulator.GameBoy.CPU.Register;
using Emulator.GameBoy.Memory;
#if DEBUG
using Emulator.DebugHelper;
#endif

namespace Emulator.GameBoy.CPU
{
    public class GameBoyCPU
    {
        private Func<UInt16, byte> ReadMemory;
        private Action<UInt16, byte> WriteMemory;
        GameBoyCPURegister[] xRegisters = new GameBoyCPURegister[8];
        GameBoyCPUDecoder xDecoder = new GameBoyCPUDecoder();

#if DEBUG
        StringBuilder xCPUErrorContainer = new StringBuilder();
        public event Action<string> RunningInstructionEvent;
        string xErrorPrefix = "GameBoy CPU Exception:";
#endif

        public bool HaltForInterrupt { get; set; }
        public int LastInstructionCycle { get; set; }

        public bool StopIncreasePC { get; set; }
        //public bool InterruptFlag { get; set; }

        public UInt16 StackPointer{get;set;}
        public UInt16 ProgramCounter
        {
            get;
            set;
        }

        public int IOBaseAddress { get { return GBMemoryConfig.IOBaseAddress; } }

        public UInt16 HLRegister { get { 
            return (UInt16)((ReadRegister8(GameBoyConfig.GBCPURegister.H)<<8)+ReadRegister8(GameBoyConfig.GBCPURegister.L));
        }
            set { 
                WriteRegister8(GameBoyConfig.GBCPURegister.H,(byte)(value>>8));
                WriteRegister8(GameBoyConfig.GBCPURegister.L, (byte)value);
            }
        }

        byte xOpCodeRegister = 0x00;

        GameBoyInstruction xInstructionRegister;

        public GameBoyCPU(GameBoyMemory aMemory)
        {
            ReadMemory = aMemory.Read8;
            WriteMemory = aMemory.Write8;
            LastInstructionCycle = 0;
            for (int i = 0; i < xRegisters.Length; i++)
                xRegisters[i] = new GameBoyCPURegister();
            ResetCPU();
        }

        public void ResetCPU()
        {
            StopIncreasePC = false;
            //AF=$01B0
            //BC=$0013
            //DE=$00D8
            //HL=$014D
            xRegisters[0x0].Value=0x00;//B
            xRegisters[0x1].Value=0x13;//C
            xRegisters[0x2].Value=0x00;//D
            xRegisters[0x3].Value=0xD8;//E
            xRegisters[0x4].Value=0x01;//H
            xRegisters[0x5].Value=0x4D;//L
            xRegisters[0x6].Value=0xB0;//F
            xRegisters[0x7].Value=0x01;//A

            StackPointer = 0xFFFE;
            ProgramCounter = 0x100;
        }

        public void Run(GameBoyMemory aGBMemory,TimeSpan aElspedTime)
        {
            ReadAndMoveForward();
            DecodeInstruction();
            RunInstruction(aGBMemory);
            LastInstructionCycle = xInstructionRegister.InstructionTime;
        }

        public CPUInstructionBase DecodeInstruction()
        {
            xInstructionRegister = xDecoder.Decode(xOpCodeRegister, this);
            return xInstructionRegister;
        }

        public void PushStack(byte aValue)
        {
                --StackPointer;
                WriteMemory(StackPointer, aValue);
        }

        public void PushStack(UInt16 aValue)
        {
            PushStack((byte)(aValue >> 8));
            PushStack((byte)(aValue & 0xFF));
        }

        public byte PopStack()
        {
                byte mResult = ReadMemory(StackPointer);
                ++StackPointer;
                return mResult;
        }

        public byte ReadAndMoveForward()
        {
                xOpCodeRegister = ReadMemory(ProgramCounter);
                if (StopIncreasePC)
                    StopIncreasePC = false;
                else
                    ++ProgramCounter;
                return xOpCodeRegister;
        }

        public UInt16 ReadMemory16()
        { 
            byte[] mResults = new byte[2];
            mResults[0] = ReadAndMoveForward();
            mResults[1] = ReadAndMoveForward();
            return (UInt16)((mResults[1]<<8)+mResults[0]);
        }

        private GBInstructionResult RunInstruction(GameBoyMemory aGBMemory)
        {           
            if (xInstructionRegister == null)
                throw new GameBoyCPUException("Unkown Instruction at address" + ProgramCounter.ToString());
#if DEBUG
            
            try
            {
#endif
                GBInstructionResult mRunningResult = null;
                if (HaltForInterrupt && (aGBMemory.IERegister == 0))
                    mRunningResult = InstructionMapper.SinglOpCodeConfig[0].PerformOperation(this, aGBMemory);//nop instruction
                else
                    mRunningResult = xInstructionRegister.PerformOperation(this, aGBMemory);
                //check interrupt request
                foreach (KeyValuePair<int, Interrupts.InterruptBase> aInterruptItem in Interrupts.InterruptTable.InterruptDic)
                {
                    if ((aGBMemory.IERegister & aInterruptItem.Key) == aInterruptItem.Key
                        && (aGBMemory.InterruptRequestFlag & aInterruptItem.Key) == aInterruptItem.Key)
                    {
                        WriteMemory(0xFFFF, 0);//reset IME flag and prevent all interrupts
                        aInterruptItem.Value.RunInterrupt(this);
                        WriteMemory(0xFF0F, (byte)(aGBMemory.InterruptRequestFlag ^ aInterruptItem.Key));//resetting of the IF register, which was the cause of the interrupt
                        HaltForInterrupt = false;
                    }
                }
#if DEBUG
                StringBuilder mSB = new StringBuilder();
                mSB.Append(xInstructionRegister.InstructionName);
                mSB.Append(" | Debug Info: ");
                mSB.Append(mRunningResult.DebugInfo);
                if (RunningInstructionEvent != null)
                    RunningInstructionEvent(mSB.ToString());
#endif
                return mRunningResult;
#if DEBUG
            }
            catch (GameBoyCPUException exp)
            {
                xCPUErrorContainer.Append(xErrorPrefix);
                xCPUErrorContainer.Append(exp.Message);
                xCPUErrorContainer.Append("\r\n");
                return null;
            }
#endif
        }

        public int ReadRegister8(int aRegister)
        {
            return xRegisters[aRegister].Value;
        }

        public void WriteRegister8(int aRegisterIndex, int aValue)
        {
            if (aValue > 0xFF)
                aValue = (byte)aValue;
            xRegisters[aRegisterIndex].Value = aValue;
        }

        public void WriteRegister16(int aHighRegisterIndex, int aLowRegisterIndex, int aValue)
        {
            if (aValue > 0xFFFF)
                throw new GameBoyCPUException("value is over flow than 0xFFFF");
            xRegisters[aHighRegisterIndex].Value = (byte)(aValue >> 8);
            xRegisters[aLowRegisterIndex].Value = (byte)aValue;
        }

        public UInt16 ReadRegister16(int aHighRegIndex, int aLowRegIndex)
        {
            return (UInt16)((ReadRegister8(aHighRegIndex) << 8) | ReadRegister8(aLowRegIndex));
        }

        public void ResetRegister(int aRegisterIndex, int aValue)
        {
            xRegisters[aRegisterIndex].Value = xRegisters[aRegisterIndex].Value ^ aValue;

        }

        #region flag
        public int FlagRegister
        {
            get { return xRegisters[GBCPURegister.F].Value; }
        }

        public bool ZFlag
        {
            get
            {
                return (xRegisters[GBCPURegister.F].Value & 128) == 128 ;
            }
            set { SetFlag(value, 128); }
        }
        public bool NFlag
        {
            get
            {
                return (xRegisters[GBCPURegister.F].Value & 64) == 64 ;
            }
            set { SetFlag(value, 64); }
        }
        public bool HFlag
        {
            get
            {
                return (xRegisters[GBCPURegister.F].Value & 32) == 32 ;
            }
            set { SetFlag(value, 32); }
        }
        public bool CFlag
        {
            get {
                return (xRegisters[GBCPURegister.F].Value & 16)==16;
        }
            set { SetFlag(value,16); }
        }

        public void SetRegister(int aRegisterIndex, int aValue)
        {
            xRegisters[aRegisterIndex].Value = xRegisters[aRegisterIndex].Value & aValue;
        }


        private void SetFlag(bool aValue, int aMask)
        {
            if (aValue)
                xRegisters[GBCPURegister.F].Value = (xRegisters[GBCPURegister.F].Value |aMask);
            else
                xRegisters[GBCPURegister.F].Value = (xRegisters[GBCPURegister.F].Value & (~aMask));

        }
        #endregion

#if DEBUG
        public string OutputCPUError()
        {
            return xCPUErrorContainer.ToString();
        }

        public string OutputCPUDebugData()
        {
            StringBuilder mSB = new StringBuilder();
            mSB.Append("Register A:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.A)).ToBinaryString());
            mSB.Append("Register B:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.B)).ToBinaryString()); 
            mSB.Append("Register C:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.C)).ToBinaryString());
            mSB.Append("Register D:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.D)).ToBinaryString()); 
            mSB.Append("Register E:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.E)).ToBinaryString()); 
            mSB.Append("Register F:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.F)).ToBinaryString());
            mSB.Append("Register H:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.H)).ToBinaryString());
            mSB.Append("Register L:");
            mSB.AppendLine(((byte)ReadRegister8(GBCPURegister.L)).ToBinaryString());

            mSB.Append("SP:");
            mSB.AppendLine(Convert.ToString(StackPointer,16).PadLeft(4,'0'));

            mSB.Append("PC:");
            mSB.AppendLine(Convert.ToString(ProgramCounter, 16).PadLeft(4, '0'));

            return mSB.ToString();
        }
#endif
    }
}
